##
# display_process.py - Created by Timothy Morey on 5/24/2012
#

import ctypes
import subprocess
import time

from libnvn import client
from libnvn import color_ramp
from libnvn import data_schema
from libnvn import data_source
from libnvn import grid_mask
from libnvn import layer
from libnvn import messages
from libnvn import variant


class DisplayProcess:
    ''' Represents an instance of the nvn-display process.

    This object helps communicate with and manage an nvn-display process running
    on the local machine.
    '''
    
    def __init__(self, id, hostAddr = 'localhost', chattyPort = None, 
                 x = None, y = None, width = None, height = None, 
                 borderless = None):

        if (chattyPort is not None and 
            0 == client.PingServers(hostAddr, chattyPort)):
            # Then the nvn-display process is already running, and we just need
            # to connect to it

            print 'Found existing nvn-display process'
            self.Process = None
            # TODO: do something with x,y,width,height,borderless?

        else:
            # Then we should launch a new nvn-display process

            cl = ['nvn-display']
            if chattyPort is not None:
                cl.append('-chattyport')
                cl.append(str(chattyPort))
            if x is not None:
                cl.append('-x')
                cl.append(str(x))
            if y is not None:
                cl.append('-y')
                cl.append(str(y))
            if width is not None:
                cl.append('-width')
                cl.append(str(width))
            if height is not None:
                cl.append('-height')
                cl.append(str(height))
            if borderless is not None and bool(borderless):
                cl.append('-borderless')
            print 'Launching new instance of nvn-display...'
            self.Process = subprocess.Popen(cl, stdout=subprocess.PIPE)

            waiting = True
            while waiting:
                line = self.Process.stdout.readline().strip()
                print line
                if line.startswith("Ready"):
                    chattyPort = line[line.find('=')+1:]
                    waiting = False

            # We no longer are interested in the stdout, so set it to None.  If
            # we don't do this, then the display process may fill up the pipe
            # buffers and get blocked while trying to write output.
            self.Process.stdout = None

        self.ID = id
        self.HostAddress = hostAddr
        self.ChattyPort = chattyPort

        self.RequestLen = 4096
        self.RequestBuffer = ctypes.create_string_buffer(self.RequestLen)

        self.ResponseLen = 4096
        self.ResponseBuffer = ctypes.create_string_buffer(self.ResponseLen)


    def AddLayer(self, datasource, xdim = None, ydim = None, tdim = None):

        schema = data_schema.DataGridSchema()
        if 0 == data_source.DataSourceGetGridSchema(datasource, ctypes.byref(schema)):

            for i in range(schema.NDims):
                if tdim is None and schema.Dims[i].IsUnlimited:
                    tdim = i
                elif tdim == schema.Dims[i].Name:
                    tdim = i
                elif xdim is None and schema.Dims[i].Name == 'x' or schema.Dims[i].Name == 'X':
                    xdim = i
                elif xdim == schema.Dims[i].Name:
                    xdim = i
                elif ydim is None and schema.Dims[i].Name == 'y' or schema.Dims[i].Name == 'Y':
                    ydim = i
                elif ydim == schema.Dims[i].Name:
                    ydim = i

            props = layer.LayerProperties()
            props.RendererType = layer.RendererTypeShadedGrid
            props.ColorRamp = color_ramp.DefaultColorRamp
            props.DataSource = datasource
            props.DataSchema = schema
            props.XDimension = xdim
            props.YDimension = ydim
            props.TDimension = tdim
            props.MaintainAspectRatio = 1
            props.UseMask = 0

            reqlen = ctypes.c_int(self.RequestLen)
            resplen = ctypes.c_int(self.ResponseLen)
            messages.CreateAddLayerRequest(props, self.RequestBuffer, ctypes.byref(reqlen))
            self.SendRequest(self.RequestBuffer, reqlen, self.ResponseBuffer, resplen)


    def AddMaskedLayer(self, datasource, masksource, xdim = None, ydim = None, tdim = None):
        
        valueschema = data_schema.DataGridSchema()
        if 0 != data_source.DataSourceGetGridSchema(datasource, ctypes.byref(valueschema)):
            valueschema = None
            
        maskschema = data_schema.DataGridSchema()
        if 0 != data_source.DataSourceGetGridSchema(masksource, ctypes.byref(maskschema)):
            maskschema = None

        if maskschema is None:
            return self.AddLayer(datasource)
        elif valueschema is not None:
            for i in range(valueschema.NDims):
                if tdim is None and valueschema.Dims[i].IsUnlimited:
                    tdim = i
                elif tdim == valueschema.Dims[i].Name:
                    tdim = i
                elif xdim is None and valueschema.Dims[i].Name == 'x' or valueschema.Dims[i].Name == 'X':
                    xdim = i
                elif xdim == valueschema.Dims[i].Name:
                    xdim = i
                elif ydim is None and valueschema.Dims[i].Name == 'y' or valueschema.Dims[i].Name == 'Y':
                    ydim = i
                elif ydim == valueschema.Dims[i].Name:
                    ydim = i

            props = layer.LayerProperties()
            props.RendererType = layer.RendererTypeShadedGrid
            props.ColorRamp = color_ramp.DefaultColorRamp
            props.DataSource = datasource
            props.DataSchema = valueschema
            props.XDimension = xdim
            props.YDimension = ydim
            props.TDimsension = tdim
            props.MaintainAspectRatio = 1
            props.UseMask = 1
            props.MaskDef.MaskMin.Type = variant.VariantTypeByte
            props.MaskDef.MaskMin.Value = variant.VariantValue(ByteVal = 2)
            props.MaskDef.MaskMax.Type = variant.VariantTypeByte
            props.MaskDef.MaskMax.Value = variant.VariantValue(ByteVal = 2)
            props.MaskDef.Negative = 0
            props.MaskSource = masksource
            props.MaskSchema = maskschema

            reqlen = ctypes.c_int(self.RequestLen)
            resplen = ctypes.c_int(self.ResponseLen)
            messages.CreateAddLayerRequest(props, self.RequestBuffer, ctypes.byref(reqlen))
            self.SendRequest(self.RequestBuffer, reqlen, self.ResponseBuffer, resplen)


    def AdjustWindow(self, x, y, width, height, borderless):
        reqlen = ctypes.c_int(self.RequestLen)
        resplen = ctypes.c_int(self.ResponseLen)
        messages.CreateAdjustDisplayRequest(x, y, width, height, borderless,
                                            self.RequestBuffer, ctypes.byref(reqlen))
        self.SendRequest(self.RequestBuffer, reqlen, self.ResponseBuffer, resplen)

    
    def Animate(self):
        frame = 0
        count = self.GetFrameCount()
        while frame < count:
            #print 'drawing frame', frame,
            self.SetCurrentFrame(frame)
            #print 'of',
            count = self.GetFrameCount()
            #print count
            frame += 1
            time.sleep(0.1)


    def CloseDisplay(self):
        reqlen = ctypes.c_int(self.RequestLen)
        resplen = ctypes.c_int(self.ResponseLen)
        messages.CreateCloseDisplayRequest(self.RequestBuffer, ctypes.byref(reqlen))
        self.SendRequest(self.RequestBuffer, reqlen, self.ResponseBuffer, resplen)

        if self.Process is not None:
            self.Process.wait()


    def GetFrameCount(self):
        reqlen = ctypes.c_int(self.RequestLen)
        resplen = ctypes.c_int(self.ResponseLen)
        messages.CreateSimpleRequest('GetFrameCount', 
                                     '', 
                                     self.RequestBuffer, 
                                     ctypes.byref(reqlen))
        self.SendRequest(self.RequestBuffer, 
                         reqlen, 
                         self.ResponseBuffer, 
                         resplen)
        msglen = ctypes.c_int(32)
        msg = ctypes.create_string_buffer(msglen.value)
        messages.ParseSimpleResponse(self.ResponseBuffer, 
                                     resplen, 
                                     ctypes.POINTER(ctypes.c_int)(), 
                                     ctypes.POINTER(ctypes.c_char)(), 
                                     0, 
                                     msg, 
                                     msglen)
        return int(msg.value)
        

    def SetCurrentFrame(self, frame):
        reqlen = ctypes.c_int(self.RequestLen)
        resplen = ctypes.c_int(self.ResponseLen)
        messages.CreateSimpleRequest('SetCurrentFrame', str(frame),
                                     self.RequestBuffer, ctypes.byref(reqlen))
        self.SendRequest(self.RequestBuffer, reqlen, self.ResponseBuffer, resplen)


    def SendRequest(self, req, reqlen, resp, resplen):
        c = ctypes.pointer(client.Client())
        client.ConnectClient(self.HostAddress, self.ChattyPort, ctypes.byref(c))
        if bool(c):
            if 0 != client.SendAndReceive(c, req, reqlen, resp, ctypes.byref(resplen)):
                print 'Error communicating with client',
                print str(self.HostAddress) + ':' + str(self.ChattyPort)

            client.DestroyClient(c)
